/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ania2.engine;

import ania2.BoardPosition;
import ania2.BrazilianMoveGenerator2;
import ania2.Move;
import ania2.MoveGenerator;
import ania2.engine.utils.TableEntryType;
import ania2.engine.utils.TranspositionTable;
import ania2.engine.utils.TranspositionTableEntry;
import java.util.List;
import javax.swing.SwingWorker;

/**
 *
 * @author pawel
 */
public class Engine2 extends SwingWorker<Move, List<Move>> {

    private MoveGenerator moveGenerator;
    private PositionEstimator positionEstimator;
    private static final int MAX = 100000;
    private static final int MIN = -100000;
    private int DEPTH = 30;
    private BoardPosition position;
    private boolean whiteTurn;
    private long availableTime;
    private TranspositionTable whiteTranspositionTable;
    private TranspositionTable blackTranspositionTable;

    public Engine2(BoardPosition position, boolean whiteTurn, long availableTime,
            TranspositionTable whiteTranspositionTable, TranspositionTable blackTranspositionTable,
            PositionEstimator positionEstimator) {
        this(position, whiteTurn, availableTime, whiteTranspositionTable, blackTranspositionTable);
        this.positionEstimator = positionEstimator;
    }

    public Engine2(BoardPosition position, boolean whiteTurn, long availableTime,
            TranspositionTable whiteTranspositionTable, TranspositionTable blackTranspositionTable) {
        this.position = position;
        this.whiteTurn = whiteTurn;
        this.availableTime = availableTime;
        this.whiteTranspositionTable = whiteTranspositionTable;
        this.blackTranspositionTable = blackTranspositionTable;
        moveGenerator = new BrazilianMoveGenerator2();
        positionEstimator = new AdvancedPositionEstimator();
    }

    private Move getMove(BoardPosition position, boolean whiteTurn, long availableTime) {
        System.out.println("--------------------------------");
        long startTime = System.currentTimeMillis();
        long endTime = startTime + availableTime;
        int currentDepth = 0;
        int bestIndex = 0;
        // List<Move> moves = new ArrayList<Move>();
        List<Move> possibleMoves = moveGenerator.generateMoves(position, whiteTurn);
        if(possibleMoves.isEmpty())
            return null;
        Move choosen = possibleMoves.get(0);

        int alpha = MIN;


        if (possibleMoves.size() > 1) {

            while (System.currentTimeMillis() < endTime && ++currentDepth < DEPTH) {
                for (int i = 0; i < possibleMoves.size(); i++) {
                    Move move = possibleMoves.get(i);
                    int val = -alphaBeta(move.getPosition(), !whiteTurn, currentDepth, MIN, MAX);
                    if (val > alpha) {
                        bestIndex = i;
                        alpha = val;
                    }

                    //System.out.printf("\t%s %12d\n", move.getShortNotation(), val);
                    move.setValue(val);
                    if (val >= MAX) { // Gracz wygrał
                        break;
                    }
                    if (System.currentTimeMillis() > endTime) {
                        System.out.println("ply: " + (currentDepth - 1));
                        System.out.println("time: " + (System.currentTimeMillis() - startTime));
                        return possibleMoves.get(0);
                    }
                }
                //System.out.println("*");
                alpha = MIN;
                if (bestIndex != 0) {
                    swap(possibleMoves, bestIndex);
                    bestIndex = 0;
                }
                publish(possibleMoves);
            }
            System.out.println("głębokość: " + currentDepth);
            System.out.println("czas: " + (System.currentTimeMillis() - startTime));
            choosen = possibleMoves.get(0);
        }

        return choosen;
    }

    private int alphaBeta(BoardPosition p, boolean whiteTurn, int depth, int alpha, int beta) {
        TranspositionTable transpositionTable = (whiteTurn) ? whiteTranspositionTable : blackTranspositionTable;
//Sprawdzenie czy pozycja policzona i zapamiętana w tablicy haszującej
        int bestMoveHashCode = 0; //pozycja po najlepszym ruchu
        TranspositionTableEntry entry = transpositionTable.lookUp(p);
        if (entry != null) {
            if (entry.getDepth() >= depth) {
                int score = entry.getValue();
                switch (entry.getType()) {
                    case EXACT_SCORE:
                        return score;
                    case UPPER_BOUND:
                        if (score <= alpha) {
                            return score;
                        }
                        if (score < beta) {
                            beta = score;
                        }
                        break;
                    case LOWER_BOUND:
                        if (score >= beta) {
                            return score;
                        }
                        if (score > alpha) {
                            alpha = score;
                        }
                        break;
                }
            } else {
                bestMoveHashCode = entry.getBestMoveHashCode();
            }
        }
        if (depth <= 0) {
            //int a = positionEstimator.estimatePosition(p, whiteTurn);
            int a = quiescenceSearch(p, whiteTurn, depth, alpha, beta);
            //System.out.println("a: "+a);
            store(p, a, depth, TableEntryType.EXACT_SCORE, bestMoveHashCode, transpositionTable);
            //return whiteTurn ? a : -a;
            return a;
        } else {
            TableEntryType hashType = TableEntryType.UPPER_BOUND;
            List<Move> moves = moveGenerator.generateMoves(p, whiteTurn);
            if (moves.isEmpty()) {
                return MIN;
            }
            if (bestMoveHashCode != 0) {

                /*System.out.println("*************");
                System.out.println("pozycja: "+p.toString());
                System.out.println("najlepszy: "+bestMove.getNotation());
                System.out.println("1 przed: "+moves.get(0).getNotation());*/
                swapBest(moves, bestMoveHashCode);
            /*System.out.println("1 po: "+moves.get(0).getNotation());
            System.out.println("*************");*/
            }

            boolean found = false;
            for (int i = 0; i < moves.size(); i++) {
                Move m = moves.get(i);
                int val = MIN;
                if (found) {
                    val = -alphaBeta(m.getPosition(), !whiteTurn, depth - 1, -alpha - 1, -alpha);
                    if ((val > alpha) && (val < beta)) // Trzeba obliczyć ponownie
                    {
                        val = -alphaBeta(m.getPosition(), !whiteTurn, depth - 1, -beta, -alpha); // full re-search
                    }
                } else {
                    val = -alphaBeta(m.getPosition(), !whiteTurn, depth - 1, -beta, -alpha);
                }
                if (val >= beta) {  // Beta cut-off
                    store(p, val, depth, TableEntryType.LOWER_BOUND, bestMoveHashCode, transpositionTable);
                    return val;
                }
                if (val > alpha) {
                    alpha = val;
                    found = true; // NegaScout
//                    bestMoveIndex = i;
                    bestMoveHashCode = m.getPosition().getHashCode();
                    hashType = TableEntryType.EXACT_SCORE;
                }
            }
            store(p, alpha, depth, hashType, bestMoveHashCode, transpositionTable);
            return alpha;
        }
    }

    private void store(BoardPosition position, int score, int depth, 
            TableEntryType type, int bestMoveHashCode, TranspositionTable table) {
        TranspositionTableEntry entry = new TranspositionTableEntry(score, depth, type, position.getLockCode(), bestMoveHashCode);
        table.store(position, entry);
    }

    private void swap(List<Move> list, int index) {
        Move first = list.set(0, list.get(index));
        list.set(index, first);
        if (list.size() > 3 && index > 1) {
            Move second = list.set(1, list.get(index));
            list.set(index, second);
        }
    }

    private void swapBest(List<Move> list, int bestMoveHashCode) {
        int bestIndex = 0;
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).getPosition().getHashCode()==bestMoveHashCode) {
                bestIndex = i;
                break;
            }
        }
        if (bestIndex > 0) {
            swap(list, bestIndex);
        }
    }

    @Override
    protected Move doInBackground() throws Exception {
        return getMove(position, whiteTurn, availableTime);
    }

    private int quiescenceSearch(BoardPosition position, boolean whiteTurn, int depth, int alpha, int beta) {
        //System.out.println(position.toString());
        List<Move> captures = moveGenerator.generateCaptures(position, whiteTurn);
        if(captures.isEmpty()) {
            int a = positionEstimator.estimatePosition(position, whiteTurn);
            //store(position, a, depth, TableEntryType.EXACT_SCORE, null, transpositionTable);
            return a;
        } else {
            //System.out.println("non empty");
            for(Move m: captures) {
                int val=-quiescenceSearch(m.getPosition(), !whiteTurn, depth, -beta, -alpha);
                //System.out.println(val);
                if (val >= beta) {  // Beta cut-off
                    //store(p, val, depth, TableEntryType.LOWER_BOUND, bestMove, transpositionTable);
                    return val;
                }
                if (val > alpha) {
                    alpha = val;
                    //bestMove = m.getPosition();
                    //hashType = TableEntryType.EXACT_SCORE;
                }
            }
        }
        return alpha;
    }
}
